// @ts-check

var SETTINGS = {
    rack_rotating: true,
    rack_rotation_speed: 0.01,
    show_labels: true,
    fullscreen: false,
    // background_color: new BABYLON.Color4(0, 0, 0, 0.8),
    background_color: new BABYLON.Color4(0.83, 0.83, 0.83, 1),
    highlight_color: new BABYLON.Color3(1, 0.8, 0),
    // label_color: '#1E90FF'
    label_color: '#464646',
    u: 46.5,
    fan_size: 140,
    fan_cage_gap: 27.5,
    chassis_offset_y: 205,
    fan_rotation_speed: 0.3,
    nodes_n: 20,
    total_n: 46,
    psu_n: 10,
    presentation: false
}
var scene = null;
var camera = null;
var hl = null;

var running_animation = [];
// TODO: filter label: nodea/all
var labels = [];
var hl_parts = [];

var rb = null;
var chassis = null;
var chassis2 = null;
var fanTray = null;
var fans = [];
var rbp = null;
var ps = null;
var rmc = null;
var psu = null;

var air_flow = [];

// settimeout timer
var timer = null;
var timer_on = false;

// TODO: show power shelf back side by set d3u visibily < 1
var d3u = null;
var presentation_animations_all = [];
var presentation_animations = [];

window.onload = createScene;

function createScene() {
    if (BABYLON.Engine.isSupported()) {
        var canvas = document.getElementById("renderCanvas");
        if (canvas instanceof HTMLCanvasElement) {
            var engine = new BABYLON.Engine(canvas, true, { stencil: true });
            scene = new BABYLON.Scene(engine);
            scene.clearColor = SETTINGS.background_color;
            hl = new BABYLON.HighlightLayer('highlight', scene);

            var light1 = new BABYLON.HemisphericLight('light1', new BABYLON.Vector3(-3000, 3000, 0), scene);
            light1.intensity = 1;
            var light2 = new BABYLON.HemisphericLight('light2', new BABYLON.Vector3(3000, -3000, 0), scene);
            light2.intensity = 1;
            var light3 = new BABYLON.HemisphericLight('light3', new BABYLON.Vector3(-3000, 300, 3000), scene);
            light3.intensity = 2.5;
            var light4 = new BABYLON.HemisphericLight('light4', new BABYLON.Vector3(3000, -300, -3000), scene);
            light4.intensity = 8;

            // rack box
            rb = new rack.box('rack', scene);
            rb.position.y = -65;
            rb.position.z = 210;

            chassis = new node3_chassis_tray_mlb_cpu_dimm_x3.chassis('1u3n', scene);
            // var chassis = new node3_chassis_tray_mlb_cpu_dimm_x3_2.chassis('1u3n', scene);
            // move chassis to 20U
            chassis.position.y -= SETTINGS.chassis_offset_y + SETTINGS.u;
            make_dummyU();
            ps = make_powerShelf();
            rmc = ps.rmc;
            psu = ps.psu;

            fanTray = new fan.fanTray('fantray1', scene);
            fanTray.position.z = 700;
            fanTray.position.y = (chassis.position.y + SETTINGS.u / 2) - SETTINGS.u * 2;

            camera = new BABYLON.ArcRotateCamera("camera1", 0, 0, 0, BABYLON.Vector3.Zero(), scene);
            camera.attachControl(canvas);

            create_sop_plug_tray(chassis);
            create_sop_plug_node(chassis.nodeA);
            create_sop_replace_cpu(chassis.nodeA.MLB.CPU1_HS.CPU1);
            create_sop_replace_dimm(chassis.nodeA.MLB.DIMM_A0);
            create_sop_plug_fantray(fanTray);
            create_sop_replace_rmc(rmc);
            create_sop_replace_psu(psu);

            var advancedTexture = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI("ui1");
            createLabel(advancedTexture, chassis, '1U3N Chassis', '0', '-80px');
            createLabel(advancedTexture, chassis.nodeA, 'Node A', '0', '-50px');
            createLabel(advancedTexture, chassis.nodeA_001, 'Node B', '0', '-50px');
            createLabel(advancedTexture, chassis.nodeA_002, 'Node C', '0', '-50px');
            createLabel(advancedTexture, chassis.nodeA.MLB.CPU1_HS.CPU1, 'CPU', '0', '-50px');
            createLabel(advancedTexture, chassis.nodeA.MLB.DIMM_A0, 'DIMM', '-30px', '-20px');

            // var dl = new BABYLON.DebugLayer(scene);
            // dl.show();

            setTimeout(function () {
                make_rack(chassis);
                make_fanwall(fanTray);
                create_sop_plug_fan(fanTray);
                create_sop_replace_rbp(rbp);
                show_rack();
                handle_hashtag();
                var postProcess = new BABYLON.FxaaPostProcess("fxaa", 1.0, null, null, engine, true);
            }, 10);

            engine.runRenderLoop(function () {
                if (SETTINGS.rack_rotating) {
                    camera.alpha += SETTINGS.rack_rotation_speed;
                }
                fans.forEach(function (i) {
                    if (i.is_rotating) {
                        i.addRotation(0, SETTINGS.fan_rotation_speed, 0);
                    }
                });
                scene.render();
            });

            window.addEventListener('resize', function () {
                engine.resize();
            });
            window.addEventListener("keyup", function (evt) {
                if (evt.key == 'P') {
                    toggle_presentation();
                }
            });
        }
    }
}
function handle_hashtag() {
    if (window.location.hash) {
        var actions = [
            "show_rack",
            "show_tray",
            "show_node",
            "show_rmc",
            "show_psu",
            "show_fanTray",
            "show_fan",
            "show_rbp",
            "begin_sop_plug_tray",
            "begin_sop_plug_node",
            "begin_sop_replace_cpu",
            "begin_sop_replace_dimm",
            "begin_sop_replace_rmc",
            "begin_sop_replace_psu",
            "begin_sop_plug_fantray",
            "begin_sop_plug_fan",
            "begin_sop_replace_rbp",
            "begin_air_flow",
        ]
        var hash = window.location.hash.substring(1);
        for (var i = 0; i < actions.length; i++) {
            if (hash == actions[i]) {
                window[actions[i]]();
                break;
            }
        }
    }
}
function toggle_rotation() {
    SETTINGS.rack_rotating = SETTINGS.rack_rotating ? false : true;
}
function toggle_labels() {
    SETTINGS.show_labels = SETTINGS.show_labels ? false : true;
    show_labels();
}
function toggle_fullscreen() {
    if (!SETTINGS.fullscreen) {
        var rootDiv = document.getElementById('rootDiv');
        if (rootDiv.requestFullscreen) {
            rootDiv.requestFullscreen();
        }
        else if (rootDiv.mozRequestFullScreen) {
            rootDiv.mozRequestFullScreen();
        }
        else if (rootDiv.webkitRequestFullscreen) {
            rootDiv.webkitRequestFullscreen();
        }
        else if (rootDiv.msRequestFullscreen) {
            rootDiv.msRequestFullscreen();
        }
        SETTINGS.fullscreen = true;
    } else {
        if (document.CancelFullScreen !== undefined) {
            document.CancelFullScreen();
        } else if (document.mozCancelFullScreen !== undefined) {
            document.mozCancelFullScreen();
        } else if (document.webkitCancelFullScreen !== undefined) {
            document.webkitCancelFullScreen();
        } else if (document.msCancelFullScreen !== undefined) {
            document.msCancelFullScreen();
        }
        SETTINGS.fullscreen = false;
    }
}
function toggle_presentation() {
    if (!SETTINGS.presentation) {
        SETTINGS.presentation = true;
        toggle_fullscreen();
        document.getElementById('panel1').style.display = 'none';
        document.getElementById('panel2').style.display = 'block';
        start_animations();
    } else {
        SETTINGS.presentation = false;
        toggle_fullscreen();
        document.getElementById('panel1').style.display = 'block';
        document.getElementById('panel2').style.display = 'none';
        reset_scene();
    }
}
function start_animations() {
    var animations = [
        { a: begin_rack_rotation, t: 'Rack', d: 'Rack overview' },
        { a: begin_sop_plug_tray, t: 'Tray', d: 'Plug out tray' },
        { a: begin_sop_plug_node, t: 'Node', d: 'Plug out node' },
        {a: begin_sop_replace_cpu, t: 'CPU', d: 'Replace CPU'},
        {a: begin_sop_replace_dimm, t: 'DIMM', d: 'Replace DIMM'},
        {a: begin_sop_replace_rmc, t: 'RMC', d: 'Replace RMC'},
        {a: begin_sop_replace_psu, t: 'PSU', d: 'Replace PSU'},
        {a: begin_sop_plug_fantray, t: 'Fan Tray', d: 'Replace fan tray'},
        {a: begin_sop_plug_fan, t: 'Fan', d: 'Replace fan'},
        {a: begin_sop_replace_rbp, t: 'RBP', d: 'Replace RBP'},
        { a: begin_air_flow, t: 'Air Flow', d: 'Cool air in from front side, hot air out from rear side.' }
    ];
    presentation_animations_all = animations;
    presentation_animations = presentation_animations_all.slice(0);
    loop_animations(0);
}
function loop_animations(delay) {
    if (!SETTINGS.presentation) {
        return;
    }
    if (presentation_animations.length == 0) {
        presentation_animations = presentation_animations_all.slice(0);
    }
    var a = presentation_animations.shift();
    setTimeout(function() {
        document.getElementById('animation_title').innerText = a.t;
        document.getElementById('animation_detail').innerText = a.d;
        a.a(loop_animations);
    }, delay != null ? delay : 1500);
}
function reset_scene() {
    // SETTINGS.rotating = true;
    SETTINGS.rack_rotating = false;
    stop_camera_down_to_floor();

    clear_air_flow();
    while (running_animation.length > 0) {
        running_animation.pop().stop();
    }
    SETTINGS.rack_rotating = false;
    clear_hl();
    SETTINGS.show_labels = false;
    show_labels(false);

    // place every part to their original place
    chassis.position.z = 0;
    chassis.nodeA.position.z = -322.6925;
    chassis.nodeA.MLB.DIMM_A0.position.y = 20.1001;
    chassis.nodeA.MLB.DIMM_A0.position.x = -45.232;
    chassis.nodeA.MLB.CPU1_HS.position.y = 14.8711;
    chassis.nodeA.MLB.CPU1_HS.rotation.z = 0;
    chassis.nodeA.MLB.CPU1_HS.CPU1.position.y = -12.3171;
    chassis.nodeA.MLB.CPU1_HS.CPU1.position.x = -0.7968;
    fanTray.position.z = 700;
    fanTray.getChildren()[0].position.z = 0;
    rmc.position.z = -2;
    rbp.position.z = 625;
    psu.position.z = -202;
}
function hl_part(part) {
    hl.addMesh(part, SETTINGS.highlight_color);
    hl_parts.push(part);
}
function clear_hl() {
    while (hl_parts.length > 0) {
        hl.removeMesh(hl_parts.pop());
    }
}
function show_part(part, camera_position, need_hl) {
    reset_scene();
    if (need_hl == null) {
        need_hl = true;
    }
    if (need_hl) {
        hl_part(part);
    }
    camera.setTarget(part.position.clone());
    camera.setPosition(camera_position);
}
function show_rack() {
    show_part(rb, new BABYLON.Vector3(-1561, 544, -2920), false);
}
function show_tray(need_hl, need_label, need_out) {
    if (need_label == null) {
        need_label = true;
    }
    if (need_out == null) {
        need_out = true;
    }
    show_part(chassis, new BABYLON.Vector3(-336, 250, -1700), need_hl);

    // move tray out
    if (need_out) {
        chassis.position.z = -700;
    }

    show_labels(need_label);
}
function show_node(need_hl, need_label, need_out) {
    if (need_label == null) {
        need_label = true;
    }
    if (need_out == null) {
        need_out = true;
    }
    show_part(chassis.nodeA, new BABYLON.Vector3(-336, 250, -1700), need_hl);

    // move tray out
    if (need_out) {
        chassis.nodeA.position.z = -800;
    }

    // TODO: show only node labels
}
function show_rmc(need_hl) {
    show_part(rmc, new BABYLON.Vector3(336, 250, -1400), need_hl);
}
function show_psu(need_hl) {
    show_part(psu, new BABYLON.Vector3(336, 250, -1400), need_hl);
}
function show_fanTray(need_hl) {
    show_part(fanTray, new BABYLON.Vector3(-1371, -349, 2815), need_hl);
}
function show_fan(need_hl) {
    show_part(fanTray.getChildren()[0], new BABYLON.Vector3(-1371, -349, 2815), need_hl);
}
function show_rbp(need_hl) {
    show_part(rbp, new BABYLON.Vector3(713, 145, 1615), need_hl);
}
function show_labels(show) {
    if (show == null) {
        show = SETTINGS.show_labels;
    }
    // advancedTexture not support tag
    for (var i = 0; i < labels.length; i++) {
        labels[i].isVisible = show;
    }
}
function createLabel(advancedTexture, mesh, text, offsetx, offsety) {
    var label = new BABYLON.GUI.Rectangle("label for " + mesh.name);
    label.background = "black"
    label.height = "30px";
    label.alpha = 0.5;
    label.width = "120px";
    label.cornerRadius = 20;
    label.thickness = 1;
    label.linkOffsetX = offsetx ? offsetx : 0;
    label.linkOffsetY = offsety ? offsety : 0;

    advancedTexture.addControl(label);
    label.linkWithMesh(mesh);

    var text1 = new BABYLON.GUI.TextBlock();
    text1.text = text ? text : mesh.name;
    text1.color = "white";
    label.addControl(text1);

    // BABYLON.GUI controls do not support tags, so save them in a list
    label.isVisible = false;
    // label.isVisible = true;
    labels.push(label);
}
function create_sop_plug_tray(chassis) {
    var keys = [];

    var animation_plug_tray = new BABYLON.Animation("move_tray", "position.z", 30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
    keys = [];
    keys.push({
        frame: 0,
        value: 0
    });
    keys.push({
        frame: 60,
        value: -500
    });
    keys.push({
        frame: 80,
        value: -500
    });
    animation_plug_tray.setKeys(keys);
    chassis.animations = [];
    chassis.animations.push(animation_plug_tray);
}
function create_sop_plug_node(node) {
    var keys = [];

    var animation_plug_node = new BABYLON.Animation("move_tray", "position.z", 30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
    keys = [];
    keys.push({
        frame: 0,
        value: -322.6925
    });
    keys.push({
        frame: 60,
        value: -800
    });
    keys.push({
        frame: 80,
        value: -800
    });
    animation_plug_node.setKeys(keys);
    node.animations = [];
    node.animations.push(animation_plug_node);
}
function create_sop_replace_cpu(cpu) {
    var keys = [];

    var animation_move_cpu_hs_step1 = new BABYLON.Animation("move_cpu_hs_s1", "position.y", 30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
    keys = [];
    keys.push({
        frame: 0,
        value: 14.8711
    });
    keys.push({
        frame: 20,
        value: 14.8711
    });
    keys.push({
        frame: 30,
        value: 100
    });
    keys.push({
        // do not change position.y when changing in position.x
        frame: 90,
        value: 100
    });
    animation_move_cpu_hs_step1.setKeys(keys);
    // var animation_move_cpu_hs_step2 = new BABYLON.Animation("move_cpu_hs_s2", "position.x", 30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
    var animation_move_cpu_hs_step2 = new BABYLON.Animation("move_cpu_hs_s2", "rotation.z", 30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
    keys = [];
    keys.push({
        // idle 10 frames: 30~40
        frame: 40,
        value: 0
    });
    keys.push({
        frame: 80,
        value: -3.14
    });
    keys.push({
        // idle 10 frames: 80~90
        frame: 90,
        value: -3.14
    });
    animation_move_cpu_hs_step2.setKeys(keys);
    var cpu_hs = cpu.parent;
    cpu_hs.animations = [];
    cpu_hs.animations.push(animation_move_cpu_hs_step1);
    cpu_hs.animations.push(animation_move_cpu_hs_step2);

    var animation_move_cpu_step1 = new BABYLON.Animation("move_cpu_s1", "position.y", 30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
    keys = [];
    keys.push({
        frame: 0,
        value: -12.3171
    });
    keys.push({
        frame: 20,
        value: -12.3171
    });
    keys.push({
        frame: 30,
        value: -50
    });
    keys.push({
        // do not change position.y when changing in position.x
        frame: 90,
        value: -50
    });
    animation_move_cpu_step1.setKeys(keys);
    var animation_move_cpu_step2 = new BABYLON.Animation("move_cpu_s2", "position.x", 30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
    keys = [];
    keys.push({
        // idle 10 frames: 30~40
        frame: 40,
        value: -0.7968
    });
    keys.push({
        frame: 80,
        value: 200
    });
    keys.push({
        // idle 10 frames: 80~90
        frame: 90,
        value: 200
    });
    animation_move_cpu_step2.setKeys(keys);
    var animation_move_cpu_step3 = new BABYLON.Animation("move_cpu_s3", "visibility", 30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
    keys = [];
    keys.push({
        // idle 10 frames: 30~40
        frame: 40,
        value: 1
    });
    keys.push({
        frame: 70,
        value: 1
    });
    keys.push({
        frame: 80,
        value: 0
    });
    keys.push({
        // idle 10 frames: 80~90
        frame: 90,
        value: 0
    });
    animation_move_cpu_step3.setKeys(keys);
    cpu.animations = [];
    cpu.animations.push(animation_move_cpu_step1);
    cpu.animations.push(animation_move_cpu_step2);
    cpu.animations.push(animation_move_cpu_step3);
}
function create_sop_replace_dimm(dimm) {
    var keys = [];

    var animation_move_dimm_step1 = new BABYLON.Animation("move_dimm_s1", "position.y", 30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
    keys = [];
    keys.push({
        frame: 0,
        value: 20.1001
    });
    keys.push({
        // idle 20 frames
        frame: 20,
        value: 20.1001
    });
    keys.push({
        frame: 40,
        value: 80
    });
    keys.push({
        // do not change position.y when changing in position.x
        frame: 90,
        value: 80
    });
    animation_move_dimm_step1.setKeys(keys);
    var animation_move_dimm_step2 = new BABYLON.Animation("move_dimm_s2", "position.x", 30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
    keys = [];
    keys.push({
        // idle 10 frames
        frame: 50,
        value: -45.232
    });
    keys.push({
        frame: 80,
        value: -200
    });
    keys.push({
        // idle 10 frames
        frame: 90,
        value: -200
    });
    animation_move_dimm_step2.setKeys(keys);
    var animation_move_dimm_step3 = new BABYLON.Animation("move_dimm_s3", "visibility", 30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
    keys = [];
    keys.push({
        // idle 10 frames
        frame: 50,
        value: 1
    });
    keys.push({
        frame: 70,
        value: 1
    });
    keys.push({
        frame: 80,
        value: 0
    });
    keys.push({
        // idle 10 frames
        frame: 90,
        value: 0
    });
    animation_move_dimm_step3.setKeys(keys);
    dimm.animations = [];
    dimm.animations.push(animation_move_dimm_step1);
    dimm.animations.push(animation_move_dimm_step2);
    dimm.animations.push(animation_move_dimm_step3);
}
function create_sop_plug_fantray(fanTray) {
    var keys = [];

    var animation_move_fanTray = new BABYLON.Animation("move_fantray", "position.z", 30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
    keys = [];
    keys.push({
        frame: 0,
        value: 700
    });
    keys.push({
        frame: 60,
        value: 1000
    });
    keys.push({
        frame: 80,
        value: 1000
    });
    animation_move_fanTray.setKeys(keys);

    fanTray.animations = [];
    fanTray.animations.push(animation_move_fanTray);
}
function create_sop_plug_fan(fanTray) {
    var fan1 = fanTray.getChildren()[0];
    var keys = [];

    var animation_move_fan = new BABYLON.Animation("move_fan", "position.z", 30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
    keys = [];
    keys.push({
        frame: 0,
        value: 0
    });
    keys.push({
        frame: 60,
        value: 300
    });
    keys.push({
        frame: 80,
        value: 300
    });
    animation_move_fan.setKeys(keys);

    fan1.animations = [];
    fan1.animations.push(animation_move_fan);
}
function create_sop_replace_rmc(rmc) {
    var keys = [];

    var animation_move_rmc = new BABYLON.Animation("move_rmc", "position.z", 30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
    keys = [];
    keys.push({
        frame: 0,
        value: -2
    });
    keys.push({
        frame: 60,
        value: -500
    });
    keys.push({
        frame: 80,
        value: -500
    });
    animation_move_rmc.setKeys(keys);

    rmc.animations = [];
    rmc.animations.push(animation_move_rmc);
}
function create_sop_replace_psu(psu) {
    var keys = [];

    var animation_move_psu_s1 = new BABYLON.Animation("move_psu_s1", "position.z", 30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
    keys = [];
    keys.push({
        frame: 0,
        value: -202
    });
    keys.push({
        frame: 60,
        value: -800
    });
    keys.push({
        frame: 80,
        value: -800
    });
    animation_move_psu_s1.setKeys(keys);
    var animation_move_psu_s2 = new BABYLON.Animation("move_psu_s2", "visibility", 30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
    keys = [];
    keys.push({
        // idle 10 frames
        frame: 0,
        value: 1
    });
    keys.push({
        frame: 40,
        value: 1
    });
    keys.push({
        frame: 80,
        value: 0
    });
    animation_move_psu_s2.setKeys(keys);
    psu.animations = [];
    psu.animations.push(animation_move_psu_s1);
    psu.animations.push(animation_move_psu_s2);
}
function create_sop_replace_rbp(rbp) {
    var keys = [];

    var animation_move_rbp = new BABYLON.Animation("move_rbp", "position.z", 30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
    keys = [];
    keys.push({
        frame: 0,
        value: 625
    });
    keys.push({
        frame: 60,
        value: 1200
    });
    keys.push({
        frame: 80,
        value: 1200
    });
    animation_move_rbp.setKeys(keys);

    rbp.animations = [];
    rbp.animations.push(animation_move_rbp);
}
function prepare_sop() {
    // no hl, no label, not plug out
    show_node(false, false, false);
    // 20U position
    camera.setTarget(chassis.position.clone());
}
function begin_sop_plug_tray(onAnimationEnd) {
    prepare_sop();

    var part = chassis;
    var hlc = SETTINGS.highlight_color;
    hl.addMesh(part, hlc);
    running_animation.push(scene.beginAnimation(part, 0, 80, false, 1, function () {
        running_animation.push(scene.beginAnimation(part, 80, 0, false, 1, function () {
            hl.removeMesh(part);
            if (onAnimationEnd) {
                onAnimationEnd();
            }
        }));
    }));
}
function begin_sop_plug_node(onAnimationEnd) {
    prepare_sop();

    var part = chassis.nodeA;
    var hlc = SETTINGS.highlight_color;
    hl.addMesh(part, hlc);
    running_animation.push(scene.beginAnimation(part, 0, 80, false, 1, function () {
        running_animation.push(scene.beginAnimation(part, 80, 0, false, 1, function () {
            hl.removeMesh(part);
            if (onAnimationEnd) {
                onAnimationEnd();
            }
        }));
    }));
}
function begin_sop_plug_fantray(onAnimationEnd) {
    prepare_sop();
    show_fan();
    toggle_fanTray_rotating(fanTray, false);

    var part = fanTray;
    var hlc = SETTINGS.highlight_color;
    hl.addMesh(part, hlc);
    running_animation.push(scene.beginAnimation(part, 0, 80, false, 1, function () {
        running_animation.push(scene.beginAnimation(part, 80, 0, false, 1, function () {
            hl.removeMesh(part);
            toggle_fanTray_rotating(fanTray, true);
            if (onAnimationEnd) {
                onAnimationEnd();
            }
        }));
    }));
}
function begin_sop_plug_fan(onAnimationEnd) {
    prepare_sop();
    show_fan();
    var fan1 = fanTray.getChildren()[0];
    toggle_fan_rotating(fan1, false);

    var part = fan1;
    var hlc = SETTINGS.highlight_color;
    hl.addMesh(part, hlc);
    running_animation.push(scene.beginAnimation(part, 0, 80, false, 1, function () {
        running_animation.push(scene.beginAnimation(part, 80, 0, false, 1, function () {
            hl.removeMesh(part);
            toggle_fan_rotating(fan1, true);
            if (onAnimationEnd) {
                onAnimationEnd();
            }
        }));
    }));
}
function begin_sop_replace_rmc(onAnimationEnd) {
    prepare_sop();
    show_rmc(false);

    var part = rmc;
    var hlc = SETTINGS.highlight_color;
    hl.addMesh(part, hlc);
    ps.visibility = 0.8;
    running_animation.push(scene.beginAnimation(part, 0, 80, false, 1, function () {
        running_animation.push(scene.beginAnimation(part, 80, 0, false, 1, function () {
            hl.removeMesh(part);
            ps.visibility = 1;
            if (onAnimationEnd) {
                onAnimationEnd();
            }
        }));
    }));
}
function begin_sop_replace_psu(onAnimationEnd) {
    prepare_sop();
    show_rmc(false);

    var part = psu;
    var hlc = SETTINGS.highlight_color;
    hl.addMesh(part, hlc);
    ps.visibility = 0.8;
    running_animation.push(scene.beginAnimation(part, 0, 80, false, 1, function () {
        running_animation.push(scene.beginAnimation(part, 80, 0, false, 1, function () {
            hl.removeMesh(part);
            ps.visibility = 1;
            if (onAnimationEnd) {
                onAnimationEnd();
            }
        }));
    }));
}
function begin_sop_replace_rbp(onAnimationEnd) {
    prepare_sop();
    show_rbp(false);

    var part = rbp;
    var hlc = SETTINGS.highlight_color;
    hl.addMesh(part, hlc);
    running_animation.push(scene.beginAnimation(part, 0, 80, false, 1, function () {
        running_animation.push(scene.beginAnimation(part, 80, 0, false, 1, function () {
            hl.removeMesh(part);
            if (onAnimationEnd) {
                onAnimationEnd();
            }
        }));
    }));
}
function begin_sop_replace_cpu(onAnimationEnd) {
    prepare_sop();
    sop_replace_cpu(onAnimationEnd);
}
function begin_sop_replace_dimm(onAnimationEnd) {
    prepare_sop();
    sop_replace_dimm(onAnimationEnd);
}
function sop_replace_cpu(onAnimationEnd) {
    var hlc = SETTINGS.highlight_color;

    var node = chassis.nodeA;
    var hs = node.MLB.CPU1_HS;
    var cpu = hs.CPU1;
    hl.addMesh(node, hlc);
    running_animation.push(scene.beginAnimation(node, 0, 80, false, 1, function () {
        hl.removeMesh(node);
        hl.addMesh(hs, hlc);
        running_animation.push(scene.beginAnimation(hs, 0, 90, false, 1, function () {
            hl.removeMesh(hs);
            hl.addMesh(cpu, hlc);
            running_animation.push(scene.beginAnimation(cpu, 0, 90, false, 1, function () {
                running_animation.push(scene.beginAnimation(cpu, 90, 0, false, 1, function () {
                    hl.removeMesh(cpu);
                    hl.addMesh(hs, hlc);
                    running_animation.push(scene.beginAnimation(hs, 90, 0, false, 1, function () {
                        hl.removeMesh(hs);
                        hl.addMesh(node, hlc);
                        running_animation.push(scene.beginAnimation(node, 80, 0, false, 1, function () {
                            hl.removeMesh(node);
                            if (onAnimationEnd) {
                                onAnimationEnd();
                            }
                        }));
                    }));
                }));
            }));
        }));
    }));
}
function sop_replace_dimm(onAnimationEnd) {
    var hlc = SETTINGS.highlight_color;

    var node = chassis.nodeA;
    var dimm = node.MLB.DIMM_A0;
    hl.addMesh(node, hlc);
    running_animation.push(scene.beginAnimation(node, 0, 80, false, 1, function () {
        hl.removeMesh(node);
        hl.addMesh(dimm, hlc);
        running_animation.push(scene.beginAnimation(dimm, 0, 90, false, 1, function () {
            running_animation.push(scene.beginAnimation(dimm, 90, 0, false, 1, function () {
                hl.removeMesh(dimm);
                hl.addMesh(node, hlc);
                running_animation.push(scene.beginAnimation(node, 80, 0, false, 1, function () {
                    hl.removeMesh(node);
                    if (onAnimationEnd) {
                        onAnimationEnd();
                    }
                }));
            }));
        }));
    }));
}
function make_cpu2(cpu) {
    var cpu2 = cpu.clone('id' + Math.floor(Math.random() * 10000));
    var cpu12_gap = 150;
    cpu2.position.z += cpu12_gap;
}
function make_dimm_slots(ds) {
    var bp = ds.position;
    var d = null;
    var ds_gap = 10;
    var cpu_sides_gap = 82;
    var cpu12_gap = 150;
    for (var i = 1; i <= 3; i++) {
        d = ds.clone('id' + Math.floor(Math.random() * 10000));
        d.position.x = bp.x - i * ds_gap;
        // dimm.clone().position.x = bp.x - i*0.11;
    }
    for (var i = 1; i <= 4; i++) {
        d = ds.clone('id' + Math.floor(Math.random() * 10000));
        d.position.x = bp.x + cpu_sides_gap + i * ds_gap;
        // dimm.clone().position.x = bp.x + 0.96 + i*0.11;
    }
    for (var i = 0; i < 4; i++) {
        d = ds.clone('id' + Math.floor(Math.random() * 10000));
        d.position.x = bp.x - i * ds_gap;
        d.position.z += cpu12_gap;
    }
    for (var i = 1; i <= 4; i++) {
        d = ds.clone('id' + Math.floor(Math.random() * 10000));
        d.position.x = bp.x + cpu_sides_gap + i * ds_gap;
        d.position.z += cpu12_gap;
    }
}
function make_dimm(dimm) {
    var bp = dimm.position;
    var d = null;
    var ds_gap = 10;
    var cpu_sides_gap = 82;
    var cpu12_gap = 150;

    for (var i = 1; i <= 3; i++) {
        d = dimm.clone('id' + Math.floor(Math.random() * 10000));
        d.position.x = bp.x - i * ds_gap;
    }
    for (var i = 1; i <= 4; i++) {
        d = dimm.clone('id' + Math.floor(Math.random() * 10000));
        d.position.x = bp.x + cpu_sides_gap + i * ds_gap;
    }
    for (var i = 0; i < 4; i++) {
        d = dimm.clone('id' + Math.floor(Math.random() * 10000));
        d.position.x = bp.x - i * ds_gap;
        d.position.z += cpu12_gap;
    }
    for (var i = 1; i <= 4; i++) {
        d = dimm.clone('id' + Math.floor(Math.random() * 10000));
        d.position.x = bp.x + cpu_sides_gap + i * ds_gap;
        d.position.z += cpu12_gap;
    }
}
function make_node(node, offset) {
    var n = node.clone('id' + Math.floor(Math.random() * 10000));
    n.position.x += offset;
}
function make_chassis(chassis, n, offset, step) {
    var c = null;
    if (!offset) {
        offset = 0;
    }
    if (!step) {
        step = SETTINGS.u;
    }
    for (var i = 1; i <= n; i++) {
        // c = chassis.clone();
        // c.position.y += offset + i * (step);
        c = chassis.createInstance();
        c.position.y += offset + i * (step);
        var trays = chassis.getChildren();
        for (var j = 0; j < trays.length; j++) {
            make_tray(trays[j], c);
        }
    }
}
function make_tray(tray0, c) {
    var tray = tray0.createInstance();
    tray.parent = c;
    tray.position = tray0.position;
    var mlb0 = tray0.getChildren()[0];
    var mlb = mlb0.createInstance();
    mlb.parent = tray;
    mlb.position = mlb0.position;
    var children = mlb0.getChildren();
    for (var j = 0; j < children.length; j++) {
        // console.log(j);
        var k = children[j].createInstance();
        k.parent = mlb;
        k.position = children[j].position;
        // k.position.y += offset + i * (step);
    }
}
function make_rack(chassis) {
    chassis2 = chassis.clone();
    chassis2.position.y -= SETTINGS.u;

    // chassis, chassis2
    var chassis_present = 2;
    var chassis_n = SETTINGS.nodes_n;
    var down_half = Math.min(chassis_n - chassis_present, 20 - chassis_present);

    // down half
    make_chassis(chassis2, down_half, 0, 0 - SETTINGS.u);
    chassis_present += down_half;
    if (chassis_n > chassis_present) {
        // up half
        make_chassis(chassis2, chassis_n - chassis_present, SETTINGS.u * 4, SETTINGS.u);
    }
}
function make_fanTrays(fanTray, dummy, rbp, n, offset, step) {
    if (!offset) {
        offset = 0;
    }
    if (!step) {
        step = SETTINGS.u * 4;
    }

    var fn = fanTray.getChildren().length;
    var f0 = fanTray.getChildren()[0];
    var r0 = f0.getChildren()[0];
    var fan_gap = SETTINGS.fan_size + SETTINGS.fan_cage_gap;
    for (var i = 1; i <= n; i++) {
        var ft = fanTray.createInstance();
        var y = offset + i * (step);
        ft.position.y += y;
        var d = dummy.createInstance();
        d.position.y += y;
        var r = rbp.createInstance();
        r.position.y += y;

        for (var j = 0; j < fn; j++) {
            var f = f0.createInstance();
            f.parent = ft;
            f.position = new BABYLON.Vector3((j - 1) * fan_gap, 0, 0);
            var r = r0.createInstance();
            r.parent = f;
            r.is_rotating = true;
            fans.push(r);
        }
    }
}
function make_fanwall(fanTray) {
    var fan_gap = SETTINGS.fan_size + SETTINGS.fan_cage_gap;
    for (var i = 0; i < 3; i++) {
        var f = new fan.fanCage('fan1_cage', scene);
        f.parent = fanTray;
        f.position = new BABYLON.Vector3((i - 1) * fan_gap, 0, 0);
        var r = f.fanRotor;
        r.material.diffuseColor = BABYLON.Color3.Black();
        r.is_rotating = true;
        fans.push(r);
    }
    var p = fanTray.position;
    var dummy = new fan.dummy('dummy', scene);
    dummy.position.y = p.y;
    dummy.position.z = p.z;
    // global var rbp
    rbp = new fan.rbp('rbp', scene);
    rbp.position.y = p.y;
    rbp.position.z = p.z - (192 - 40) / 2 + 1;
    var pmat = new BABYLON.StandardMaterial('part', scene);
    pmat.diffuseColor = new BABYLON.Color3(0.03, 0.03, 0.03);
    pmat.specularColor = new BABYLON.Color3(0.03, 0.03, 0.03);
    rbp.material = pmat;

    var fanTray2 = fanTray.clone();
    fanTray2.position.y -= SETTINGS.u * 4;
    for (var i = 0; i < 3; i++) {
        var r = fanTray2.getChildren()[i].getChildren()[0];
        r.is_rotating = true;
        fans.push(r);
    }
    var dummy2 = dummy.clone();
    var rbp2 = rbp.clone();
    dummy2.position.y -= SETTINGS.u * 4;
    rbp2.position.y -= SETTINGS.u * 4;

    // fantray, fantray2
    var fanzones_present = 2;
    var fanzones_n = Math.ceil(SETTINGS.nodes_n / 4);
    var down_half = Math.min(fanzones_n - fanzones_present, 5 - fanzones_present);

    // down half
    make_fanTrays(fanTray2, dummy2, rbp2, down_half, 0, 0 - SETTINGS.u * 4);
    fanzones_present += down_half;
    if (fanzones_n > fanzones_present) {
        // up half
        make_fanTrays(fanTray2, dummy2, rbp2, fanzones_n - fanzones_present, SETTINGS.u * 7, SETTINGS.u * 4);
    }
}
function toggle_fanTray_rotating(fanTray, rotating) {
    for (var i = 0; i < 3; i++) {
        var r = fanTray.getChildren()[i].getChildren()[0];
        r.is_rotating = rotating;
    }
}
function toggle_fan_rotating(fan1, rotating) {
    var r = fan1.getChildren()[0];
    r.is_rotating = rotating;
}
function set_camera_target(direction) {
    stop_camera_down_to_floor();
    var t0 = camera.target;
    var offset = SETTINGS.u;
    if (direction == 'down') {
        offset = 0 - offset;
    }
    camera.setTarget(new BABYLON.Vector3(t0.x, t0.y + offset, t0.z));
}
function make_dummyU() {
    // front side
    var du = new dummy.dummyU('dummy1u', scene);
    du.material = rb.material;
    var p0 = chassis.position;
    var y0 = p0.y + SETTINGS.u * 4;
    du.position = new BABYLON.Vector3(p0.x, y0, -326);
    var dummy_n = SETTINGS.total_n - 6 - SETTINGS.nodes_n;
    dummy_n = Math.min(dummy_n, 20);

    for (var i = 1; i < dummy_n; i++) {
        var dui = du.createInstance();
        dui.position.y += i * SETTINGS.u;
    }

    // end side
    var d4u = new dummy.dummy4U('dummy4u', scene);
    d4u.material = rb.material;
    var p0 = chassis.position;
    var y0 = p0.y + SETTINGS.u * 5.5;
    d4u.position = new BABYLON.Vector3(p0.x, y0, 720);
    dummy_n = (SETTINGS.total_n - 6 - SETTINGS.nodes_n) / 4;
    dummy_n = Math.min(dummy_n, 5);

    for (var i = 1; i < dummy_n; i++) {
        var dui = d4u.createInstance();
        dui.position.y += i * SETTINGS.u * 4;
    }

    // global var d3u
    d3u = new dummy.dummy3U('dummy3u', scene);
    d3u.material = rb.material;
    var p0 = chassis.position;
    var y0 = p0.y + SETTINGS.u * 2;
    d3u.position = new BABYLON.Vector3(p0.x, y0, 720);
}
function make_powerShelf() {
    var ps = new power.powerShelf('ps', scene);
    // var rmc = ps.rmc;

    ps.material = rb.material.clone();
    var pmat = new BABYLON.StandardMaterial('part', scene);
    pmat.diffuseColor = new BABYLON.Color3(0.03, 0.03, 0.03);
    pmat.specularColor = new BABYLON.Color3(0.03, 0.03, 0.03);
    var hmat = new BABYLON.StandardMaterial('hole', scene);
    hmat.diffuseColor = BABYLON.Color3.White();

    ps.rmc.material = pmat;
    ps.psu.material = pmat;
    ps.rmcHole.material = hmat;
    ps.psuHole.material = hmat;

    var psu_gap = 50;
    var psu2 = ps.psu.clone();
    psu2.position.x += psu_gap;
    for (var i = 1; i <= SETTINGS.psu_n - 2; i++) {
        var psui = psu2.createInstance();
        psui.parent = psu2.parent;
        psui.position.x += i * psu_gap;
    }

    var p0 = chassis.position;
    ps.position = new BABYLON.Vector3(p0.x, p0.y + SETTINGS.u * (0.5 + 3 / 2) + 3, 73);

    return ps;
}
function camera_down(start, end, dt, ds, idle_t) {
    if (!timer_on) {
        // stop outside
        return;
    }
    var t = camera.target;
    // console.log('camera down ' + t.y + ' => ' + t.y + ds + ' <> ' + end);
    var ty = t.y + ds;
    var idle = false;
    if (ty > start) {
        // console.log('end');
        ds = start - t.y;
        timer_on = false;
    } else if (ty < end) {
        ds = 0 - ds;
        idle = true;
        // console.log('down to up ' + ds);
    }
    camera.setTarget(new BABYLON.Vector3(t.x, ty, t.z));
    var p = camera.position;
    camera.setPosition(new BABYLON.Vector3(p.x, p.y + ds, p.z));
    if (timer_on) {
        timer = setTimeout(function () {
            camera_down(start, end, dt, ds, idle_t);
        }, idle ? idle_t : dt);
    }
}
function camera_down_to_floor(v, dt, idle_t) {
    timer_on = true;
    var floor = chassis.position.y - SETTINGS.u * 10;
    var start = camera.target.y;

    if (!v) {
        // v = s/t => ds = v * dt
        // v in mm/ms
        v = 5.0 * SETTINGS.u / 1000;
    }
    // update cycle, in ms
    if (!dt) {
        dt = 30;
    }
    var ds = 0 - v * dt;
    if (!idle_t) {
        // idle time when change from down to up, in ms
        idle_t = 1000;
    }
    timer = setTimeout(function () {
        camera_down(start, floor, dt, ds, idle_t);
    }, dt);
}
function stop_camera_down_to_floor() {
    clearTimeout(timer);
    timer_on = false;
}
function begin_rack_rotation(onAnimationEnd) {
    reset_scene();
    show_rack();
    SETTINGS.rack_rotating = true;

    if (onAnimationEnd) {
        setTimeout(function () {
            onAnimationEnd();
        }, 20000);
    }
}
function begin_air_flow(onAnimationEnd) {
    reset_scene();
    var x0 = -150;
    var x_step = 180;
    var x1 = x0 + x_step * 2;
    var y0 = chassis.position.y - SETTINGS.u * 17;
    var y_step = SETTINGS.u * 4;
    var y1 = chassis.position.y - SETTINGS.u * 1;
    var z0 = -1000;
    var z1 = 1000;
    var scaling = 20;

    var len = z1 - z0;
    // var gap = 5 * scaling;
    var gap = 10 * scaling;
    var p_n = Math.floor(len / gap);
    // v = x mm/s = x mm/30frame
    // w / x frame => v
    var v = 1.5;
    var frame = Math.floor(30.0 / v);
    var fade_frames = frame * 0.2;

    make_air_flow_half('cool', scaling, x0, x1, x_step, y0, y1, y_step, z0, 0, gap, p_n / 2, frame, fade_frames, true, false);
    make_air_flow_half('hot', scaling, x0, x1, x_step, y0, y1, y_step, 0, z1, gap, p_n / 2, frame, fade_frames, false, true);

    camera.setTarget(chassis.position.clone());
    camera.setPosition(new BABYLON.Vector3(-2448, 156, -1284));
    rb.visibility = 0.2;
    chassis.visibility = 0.3;
    chassis.getChildMeshes().forEach(i => i.visibility = 0.3);
    chassis2.visibility = 0.3;
    chassis2.getChildMeshes().forEach(i => i.visibility = 0.3);
    // ps.visibility = 0.3;
    // ps.getChildMeshes().forEach(i => i.visibility = 0.3);

    if (onAnimationEnd) {
        setTimeout(function () {
            onAnimationEnd();
        }, 5000);
    }
}
function clear_air_flow() {
    while (air_flow.length > 0) {
        air_flow.pop().dispose();
    }
    // air_flow.forEach(i => i.dispose());
    rb.visibility = 1;
    chassis.visibility = 1;
    chassis.getChildMeshes().forEach(i => i.visibility = 1);
    chassis2.visibility = 1;
    chassis2.getChildMeshes().forEach(i => i.visibility = 1);
    // ps.visibility = 1;
    // ps.getChildMeshes().forEach(i => i.visibility = 0.3);
}
function make_arrow(type, x0, y0, z0, scaling) {
    var corners = [
        new BABYLON.Vector2(1, 0),
        new BABYLON.Vector2(0, 2.5),
        new BABYLON.Vector2(-1, 0),
        new BABYLON.Vector2(-0.3, 0.2),
        new BABYLON.Vector2(-0.5, -2),
        new BABYLON.Vector2(0.5, -2),
        new BABYLON.Vector2(0.3, 0.2),
    ];
    var poly_tri = new BABYLON.PolygonMeshBuilder("polytri", corners, scene);
    var p1 = poly_tri.build(null, 0.3);
    p1.scaling = new BABYLON.Vector3(scaling, scaling, scaling);
    p1.position.x = x0;
    p1.position.y = y0;
    p1.position.z = z0;
    p1.rotate(BABYLON.Axis.Z, Math.PI / 2);

    var mat = new BABYLON.StandardMaterial('arrow_' + type, scene);
    mat.specularColor = BABYLON.Color3.Black();
    if (type == 'hot') {
        mat.diffuseColor = BABYLON.Color3.Red();
    } else {
        mat.diffuseColor = BABYLON.Color3.Blue();
    }
    p1.material = mat;

    return p1;
}
function save_air_flow_arrow(p) {
    air_flow.push(p);
    // hide in power shelf
    p.position = ps.position.clone();
}
function make_air_flow_half(type, scaling, x0, x1, x_step, y0, y1, y_step, z0, z1, gap, p_n, frame, fade_frames, fade_in, fade_out) {
    var p1 = make_arrow(type, x0, y0, z0, scaling);
    save_air_flow_arrow(p1);

    var pi;
    for (var y = y0; y <= y1; y += y_step) {
        for (var x = x0; x <= x1; x += x_step) {
            for (var i = 0; i < p_n; i++) {
                var z = z0 + gap * i;
                if (fade_in && i == 0) {
                    pi = p1.clone('p' + i);
                    save_air_flow_arrow(pi);
                    add_animation(pi, 'flow', 'position.z', [[0, z], [frame, z + gap]], true);
                    add_animation(pi, 'flow', 'visibility', [[0, 0], [fade_frames, 0], [frame, 1]], false);
                } else if (fade_out && (i >= (p_n - 1))) {
                    pi = p1.clone('p' + i);
                    save_air_flow_arrow(pi);
                    add_animation(pi, 'flow', 'position.z', [[0, z], [frame, z + gap]], true);
                    add_animation(pi, 'flow', 'visibility', [[0, 1], [fade_frames, 1], [frame, 0]], false);
                } else {
                    pi = p1.createInstance('p' + i);
                    add_animation(pi, 'flow', 'position.z', [[0, z], [frame, z + gap]], true);
                }
                pi.position.z = z;
                pi.position.x = x;
                pi.position.y = y;
                scene.beginAnimation(pi, 0, frame, true);
            }
        }
    }
}
function add_animation(mesh, name, prop, values, clear_before_add) {
    var animation = new BABYLON.Animation(name, prop, 30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
    var keys = [];
    for (var i = 0; i < values.length; i++) {
        keys.push({
            frame: values[i][0],
            value: values[i][1]
        });
    }
    animation.setKeys(keys);
    if (clear_before_add) {
        mesh.animations = [];
    }
    mesh.animations.push(animation);
    // scene.beginAnimation(mesh, 0, 100, true);
}